﻿#region Header

// <file>
//     <copyright see="prj:///doc/copyright.txt"/>
//     <license see="prj:///doc/license.txt"/>
//     <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
//     <version>$Revision: 2827 $</version>
// </file>

#endregion Header

namespace ICSharpCode.TextEditor.Undo
{
    using System;
    using System.Collections.Generic;

    /// <summary>
    /// This class implements an undo stack
    /// </summary>
    public class UndoStack
    {
        #region Fields

        public TextEditorControlBase TextEditorControl = null;

        /// <summary>
        /// Gets/Sets if changes to the document are protocolled by the undo stack.
        /// Used internally to disable the undo stack temporarily while undoing an action.
        /// </summary>
        internal bool AcceptChanges = true;

        int actionCountInUndoGroup;
        Stack<IUndoableOperation> redostack = new Stack<IUndoableOperation>();
        int undoGroupDepth;
        Stack<IUndoableOperation> undostack = new Stack<IUndoableOperation>();

        #endregion Fields

        #region Events

        /// <summary>
        /// </summary>
        public event EventHandler ActionRedone;

        /// <summary>
        /// </summary>
        public event EventHandler ActionUndone;

        #endregion Events

        #region Properties

        /// <summary>
        /// Gets if there are actions on the redo stack.
        /// </summary>
        public bool CanRedo
        {
            get {
                return redostack.Count > 0;
            }
        }

        /// <summary>
        /// Gets if there are actions on the undo stack.
        /// </summary>
        public bool CanUndo
        {
            get {
                return undostack.Count > 0;
            }
        }

        /// <summary>
        /// Gets the number of actions on the redo stack.
        /// </summary>
        public int RedoItemCount
        {
            get {
                return redostack.Count;
            }
        }

        /// <summary>
        /// Gets the number of actions on the undo stack.
        /// </summary>
        public int UndoItemCount
        {
            get {
                return undostack.Count;
            }
        }

        #endregion Properties

        #region Methods

        public void AssertNoUndoGroupOpen()
        {
            if (undoGroupDepth != 0) {
                undoGroupDepth = 0;
                throw new InvalidOperationException("No undo group should be open at this point");
            }
        }

        /// <summary>
        /// Clears both the undo and redo stack.
        /// </summary>
        public void ClearAll()
        {
            AssertNoUndoGroupOpen();
            undostack.Clear();
            redostack.Clear();
            actionCountInUndoGroup = 0;
        }

        /// <summary>
        /// Call this method, if you want to clear the redo stack
        /// </summary>
        public void ClearRedoStack()
        {
            redostack.Clear();
        }

        public void EndUndoGroup()
        {
            if (undoGroupDepth == 0)
                throw new InvalidOperationException("There are no open undo groups");
            undoGroupDepth--;
            //Util.LoggingService.Debug("Close undo group (new depth=" + undoGroupDepth + ")");
            if (undoGroupDepth == 0 && actionCountInUndoGroup > 1) {
                undostack.Push(new UndoQueue(undostack, actionCountInUndoGroup));
            }
        }

        /// <summary>
        /// Call this method to push an UndoableOperation on the undostack, the redostack
        /// will be cleared, if you use this method.
        /// </summary>
        public void Push(IUndoableOperation operation)
        {
            if (operation == null) {
                throw new ArgumentNullException("operation");
            }

            if (AcceptChanges) {
                StartUndoGroup();
                undostack.Push(operation);
                actionCountInUndoGroup++;
                if (TextEditorControl != null) {
                    undostack.Push(new UndoableSetCaretPosition(this, TextEditorControl.ActiveTextAreaControl.Caret.Position));
                    actionCountInUndoGroup++;
                }
                EndUndoGroup();
                ClearRedoStack();
            }
        }

        /// <summary>
        /// Call this method to redo the last undone operation
        /// </summary>
        public void Redo()
        {
            AssertNoUndoGroupOpen();
            if (redostack.Count > 0) {
                IUndoableOperation uedit = (IUndoableOperation)redostack.Pop();
                undostack.Push(uedit);
                uedit.Redo();
                OnActionRedone();
            }
        }

        public void StartUndoGroup()
        {
            if (undoGroupDepth == 0) {
                actionCountInUndoGroup = 0;
            }
            undoGroupDepth++;
            //Util.LoggingService.Debug("Open undo group (new depth=" + undoGroupDepth + ")");
        }

        /// <summary>
        /// Call this method to undo the last operation on the stack
        /// </summary>
        public void Undo()
        {
            AssertNoUndoGroupOpen();
            if (undostack.Count > 0) {
                IUndoableOperation uedit = (IUndoableOperation)undostack.Pop();
                redostack.Push(uedit);
                uedit.Undo();
                OnActionUndone();
            }
        }

        /// <summary>
        /// </summary>
        protected void OnActionRedone()
        {
            if (ActionRedone != null) {
                ActionRedone(null, null);
            }
        }

        /// <summary>
        /// </summary>
        protected void OnActionUndone()
        {
            if (ActionUndone != null) {
                ActionUndone(null, null);
            }
        }

        #endregion Methods

        #region Nested Types

        class UndoableSetCaretPosition : IUndoableOperation
        {
            #region Fields

            TextLocation pos;
            TextLocation redoPos;
            UndoStack stack;

            #endregion Fields

            #region Constructors

            public UndoableSetCaretPosition(UndoStack stack, TextLocation pos)
            {
                this.stack = stack;
                this.pos = pos;
            }

            #endregion Constructors

            #region Methods

            public void Redo()
            {
                stack.TextEditorControl.ActiveTextAreaControl.Caret.Position = redoPos;
            }

            public void Undo()
            {
                redoPos = stack.TextEditorControl.ActiveTextAreaControl.Caret.Position;
                stack.TextEditorControl.ActiveTextAreaControl.Caret.Position = pos;
            }

            #endregion Methods
        }

        #endregion Nested Types
    }
}